package toman.hiber;

import org.hibernate.Query;
import org.hibernate.classic.Session;
import org.hibernate.type.Type;
import toman.*;
import toman.orm.TOAnnotQueryEngine;

import javax.persistence.TemporalType;
import java.util.*;

/**
 * User: zslajchrt
 * Date: 2.5.11
 * Time: 15:05
 */
public class HQueryEngine extends TOAnnotQueryEngine {

    private final Session session;

    public HQueryEngine(Session session) {
        this.session = session;
    }

    @Override
    protected <T, K> Object findInitObject(TOMan toman, Class<T> toClass, K toId, String readQueryDef, String idClauseDef) throws Exception {
        String readQuery = readQueryDef + " WHERE " + idClauseDef;
        return executeIdentityQuery(toId, readQuery);
    }

    protected <K> Object executeIdentityQuery(K toId, String readQuery) {
        Query q = session.createQuery(readQuery);
        String[] namedParameters = q.getNamedParameters();
        q.setParameter(namedParameters[0], toId);

        Object toData = q.uniqueResult();
        if (toData == null) {
            throw new IllegalArgumentException("No TO data found for query " + readQuery + " and key " + toId);
        }
        return toData;
    }

    public class HibernateQueryForUpdate extends HibernateParameterizedQuery implements QueryForUpdate {

        HibernateQueryForUpdate(TOMan toman, String queryString) {
            super(toman, queryString);
        }

        public void executeUpdate() {
            q.executeUpdate();
        }
    }

    @Override
    protected <T> QueryForUpdate createQueryForUpdate(TOMan toman, T to, Object updateQueryDef, String idClauseDef) throws Exception {
        final String writeQuery = updateQueryDef.toString() + " WHERE " + idClauseDef;
        return new HibernateQueryForUpdate(toman, writeQuery);
    }

    @Override
    protected <T> TOQuery<T> createQuery(TOMan toman, Class<T> toClass, String readQueryDef, Object condition) throws Exception {
        String query;
        if (condition == null || "".equals(condition)) {
            query = readQueryDef;
        } else {
            query = readQueryDef + " where " + condition;
        }

        return new HibernateQuery<T>(toman, toClass, query);
    }

    public class HibernateParameterizedQuery implements ParameterizedQuery {
        protected final Query q;
        private final Set<String> paramNames;
        protected final TOMan toman;

        HibernateParameterizedQuery(TOMan toman, String queryString) {
            this.toman = toman;
            q = session.createQuery(queryString);
            paramNames = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(q.getNamedParameters())));
        }

        public HibernateParameterizedQuery setParameter(String paramName, Object value) {
            q.setParameter(paramName, value);
            return this;
        }

        public HibernateParameterizedQuery setParameter(String paramName, Date value, TemporalType type) {
            switch (type) {
                case TIMESTAMP:
                    q.setTimestamp(paramName, value);
                    return this;
                case TIME:
                    q.setTime(paramName, value);
                    return this;
                case DATE:
                    q.setDate(paramName, value);
                    return this;
            }
            throw new UnsupportedOperationException();
        }

        public Set<String> getParameterNames() {
            return paramNames;
        }

        public HibernateParameterizedQuery setParameter(String paramName, Date value, Type tt) {
            q.setParameter(paramName, value, tt);
            return this;
        }

    }

    public class HibernateQuery<T> extends HibernateParameterizedQuery implements TOQuery<T> {

        private final Class<T> toClass;

        HibernateQuery(TOMan toman, Class<T> toClass, String queryString) {
            super(toman, queryString);
            this.toClass = toClass;
        }

        public HibernateQuery setFirstResult(int index) {
            q.setFirstResult(index);
            return this;
        }

        public HibernateQuery setMaxResults(int max) {
            q.setMaxResults(max);
            return this;
        }

        public TOList<T> getResults() {
            List<Object> resultList = q.list();
            return new TOList<T>(resultList, toClass, toman);
        }

        public T getSingleResult() {
            try {
                return toman.createTOInstance(toClass, q.uniqueResult());
            } catch (NonUniqueResultException e) {
                throw new TOQuery.NonUniqueResultException(e.getMessage(), e);
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        public Query getInternalQuery() {
            return q;
        }
    }

}
